package com.lagou.edu.factory;

import com.lagou.edu.annotation.*;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author 应癫
 * <p>
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {
    public static String packagePath = "com.lagou.edu";
    public static String JAVA_SUFFIX = ".class";
    public static String basePath;
    private static final String PROXY_CLASS_NAME = "proxyFactory";

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    /**
     * 存储对象
     */
    private static Map<String, Object> map = new HashMap<>();
    /**
     * 存储有autowired的对象
     */
    private static HashSet<Object> autoWiredMapSet = new HashSet();

    /**
     * 存储有transactional的对象
     */
//    private static HashSet<Object> transactionalSet = new HashSet();


    static {
        try {
            initScan();
            //处理包含autowired类
            doAutoWiredBean();
            //处理事务相关类
            doTransactionalBean();


        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static void doAutoWiredBean() throws ClassNotFoundException, IllegalAccessException {
        //属性注入
        for (Object autoWiredBean : autoWiredMapSet) {
            Class<?> aClass = autoWiredBean.getClass();
            //获取所有的属性
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                boolean annotationPresent = declaredField.isAnnotationPresent(AutoWired.class);
                if (!annotationPresent) {
                    //如果一个属性上面没有就继续
                    continue;
                }
                //判断是否有指定名称
                Qualifier annotation = declaredField.getAnnotation(Qualifier.class);
                String clazzName;
                if (null!=annotation&&null != annotation.value()&&"".equalsIgnoreCase(annotation.value())) {
                    //根据名称注入
                    clazzName = annotation.value();
                } else {
                    //根据类型注入对象
                    clazzName = getCamelClassName(declaredField.getType().getName());
                }
                //从map中取出clazzName的Object
                if (!map.containsKey(clazzName)) {
                    throw new ClassNotFoundException("no bean found in ioc by autowired");
                }
                declaredField.setAccessible(true);
                Object o = map.get(clazzName);
                declaredField.set(autoWiredBean, o);
                System.out.println(autoWiredBean);
            }
        }
    }

    private static void initScan() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //扫描包路径
        String separator = File.separator;
        String replaceAll = packagePath.replaceAll("\\.", "\\" + separator);
        //获取根路径的地址
        String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
//        String path = System.getProperty("user.dir");
        File file = new File(path + separator +replaceAll);
        basePath = file.getPath();
        System.out.println("basePath:" + basePath);
        //编写方法遍历所有的路径下的文件以及文件夹，文件夹要继续遍历，文件需要判断是不是.class结尾的
        scanBasePackage(basePath);
    }


    private static void scanBasePackage(String path) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        File file = new File(path);
        File[] files = file.listFiles();
        if (null==files){
            return;
        }
        for (File elementFile : files) {
            if (elementFile.isDirectory()) {
                //如果是文件夹就继续,递归
                String elementFilePath = elementFile.getPath();
                scanBasePackage(elementFilePath);
            }
            if (elementFile.isFile() && elementFile.getName().endsWith(JAVA_SUFFIX)) {
                //证明是java文件
                String pathT = elementFile.getPath();
                int length = basePath.length();
                String substring = pathT.substring(length);
                String classPath = packagePath + substring.replaceAll("\\" + JAVA_SUFFIX, "").
                        replaceAll("\\\\", "\\.");
                System.out.println(classPath);
                //反射获取类
                Class<?> aClass = Class.forName(classPath);
                if (aClass.isInterface()) {
                    continue;
                }
                boolean annotationPresent = aClass.isAnnotationPresent(Service.class);

                //是否被service注解修饰，只有被service修饰才能加入ioc中
                if (annotationPresent) {
                    Object o = aClass.newInstance();
                    //生成代理对象
                    doInitClass(o, aClass.getAnnotation(Service.class).value());
                    //是否被autowired修饰
                    isAutoWired(o);
                    continue;
                }
                boolean repositoryAnnotationPresent = aClass.isAnnotationPresent(Repository.class);
                if (repositoryAnnotationPresent){
                    Object o = aClass.newInstance();
                    //生成代理对象
                    doInitClass(o, aClass.getAnnotation(Repository.class).value());
                    //是否被autowired修饰
                    isAutoWired(o);
                }
            }
        }
    }

    private static void isAutoWired(Object o) throws IllegalAccessException, InstantiationException {
        Class<?> aClass = o.getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(AutoWired.class) != null) {
                autoWiredMapSet.add(o);
                break;
            }
        }
    }

    private static void doInitClass(Object o, String value) throws IllegalAccessException, InstantiationException {
        Class<?> aClass = o.getClass();
        //根据类和service上的value值加入ioc容器
        String name = aClass.getName();
        if (null == value || "".equalsIgnoreCase(value)) {
            //如果没有指定service的名称就取service的类名
            String className = getCamelClassName(name);
            //获取的是全类名
            map.put(className, o);
            //存一份只有类名驼峰的对象
            map.put(getCamelClassName(className.substring(className.lastIndexOf(".") + 1)), o);
        } else {
            map.put(value.trim(), o);
        }
        //如果类上有实现的接口，保存一份接口为名的实例化对象，防止直接通过接口注入找不到类名
        Class<?>[] interfaces = aClass.getInterfaces();
        if (interfaces.length > 0) {
            for (Class<?> classInterface : interfaces) {
                // 接口全类名为key
                String classInterfaceName = classInterface.getName();
                map.put(getCamelClassName(classInterfaceName), o);
                // 接口类名为key
                map.put(getCamelClassName(classInterfaceName.substring(classInterfaceName.lastIndexOf(".") + 1)), o);
            }
        }
    }

    /**
     * Description:获取小驼峰类名
     *
     * @param className
     * @return: java.lang.String
     * @Author: Tyrone
     * @date: 2020/11/23
     **/
    private static String getCamelClassName(String className) {
        className = className.substring(0, 1).toLowerCase() + className.substring(1);
        return className;
    }

    private static void doTransactionalBean() {
        //获取代理工厂
        ProxyFactory proxyFactory = (ProxyFactory) map.get(PROXY_CLASS_NAME);
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            Object targetObject = map.get(key);
            Class<?> aClass = targetObject.getClass();
            Method[] methods = aClass.getMethods();
            Transactional transactionalClassAnno = aClass.getAnnotation(Transactional.class);
            for (Method method : methods) {
                Transactional annotation = method.getAnnotation(Transactional.class);
                //如果类上有transactional注解
                if (null != transactionalClassAnno || null != annotation) {
                    //如果方法上面有 Transactional,看类是否有继承接口
                    Object proxyObject;
                    Class<?>[] interfaces = aClass.getInterfaces();
                    if (null != interfaces && interfaces.length > 0) {
                        //如果继承有接口就用JDK动态代理
                        proxyObject = proxyFactory.getJdkProxy(targetObject);
                    } else {
                        proxyObject = proxyFactory.getCglibProxy(targetObject);
                    }
                    map.put(key, proxyObject);
                    map.put(getCamelClassName(key.substring(key.lastIndexOf(".") + 1)), proxyObject);

                    break;
                }
            }
        }
    }


    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        return map.get(id);
    }

}
